﻿

//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

#include "ISnap.h"
#include "Snapper.h"
#include "IUiView.h"
#include "Vector3d.h"
#include "OwnerPtr.h"
#include "ActionBase.h"
#include "UiSnapUtils.h"
#include "ISnapRender.h"
#include "CommandBase.h"
#include "ISnapContext.h"
#include "ISnapSettings.h"
#include "ContainerUtil.h"
#include "ISnapCandidates.h"
#include "GcmpCommandState.h"
#include "InteractivePicker.h"
#include "UiDocumentViewUtils.h"
#include "SnapTextPositionType.h"
#include "IPickPointActionConfig.h"
#include "ISnapCandidatesPreprocessor.h"

using namespace gcmp;

namespace CodeSnipper
{
    class SampleSnapperAction : public gcmp::ActionBase
    {

    public:
        virtual bool OnMovePoint(IUiView* pCurrentView, const Vector3d& pos) override
        {
            // 创建ISnapContext，是捕捉设置输入的主入口
            OwnerPtr<ISnapContext> opSnapContext = ISnapContext::Create();
            // 使用工具类UiSnapUtils从当前IUiView对ISnapContext做初始化，例如设置IViewerContext,将ISnapContext的捕捉平面设置为视图的工作平面
            UiSnapUtils::InitSnapContextFromUiView(opSnapContext.get(), pCurrentView);

            // 获得全局的ISnapSettings，这里的程序希望在全局ISnapSettings的基础上，打开端点捕捉
            ISnapSettings* pGlobalSnapSettings = ISnapSettings::GetGlobalSnapSettings();
            // 因为对ISnapSettings会做修改，但是不希望改变全局的ISnapSettings，所以Clone一份使用
            OwnerPtr<ISnapSettings> opSnapSettings = pGlobalSnapSettings->Clone();
            // 打开端点捕捉
            opSnapSettings->SetCanSnapEndPoint(true);
            // 将ISnapSettings赋予捕捉设置主入口ISnapContext
            opSnapContext->SetSnapSettings(opSnapSettings.get());
            opSnapContext->SetInputPoint(pos);

            // 做捕捉计算，结果返回为ISnapCandidates
            OwnerPtr<ISnapCandidates> opSnapCandidates = Snapper::Snap(opSnapContext.get());

            // 获得排序在最考前的捕捉结果ISnap
            const ISnap* pSnap = opSnapCandidates->GetCurrentSnap();
            // 如果有捕捉结果
            if (pSnap)
            {
                //获得捕捉点，并且渲染该捕捉结果，例如捕捉的预高亮和文字显示
                Vector3d snappedPoint = pSnap->GetSnappedPoint();
                ISnapRender::Get()->Draw(pSnap, SnapTextPositionType::LowerRight);
                UpdateView();
            }
        }
    };

    class SamplePickPointConfigure : public IPickPointActionConfig
    {
        DEFINE_CAST_DERIVED(SamplePickPointConfigure, gcmp::IPickPointActionConfig);
    public:
        SamplePickPointConfigure() {};
        virtual ~SamplePickPointConfigure() {};

        bool ConfigSnapSettings(ISnapSettings* pSnapSetting)
        {
            // 首先关掉所有捕捉类型，然后打开需要的捕捉类型
            pSnapSetting->DisableAllSnaps();
            pSnapSetting->SetCanSnapEndPoint(true);
            pSnapSetting->SetCanSnapMiddlePoint(true);
            return true;
        }
    };

    class SamplePickPointActionAction : ActionBase
    {
    public:
        virtual void InitAction(IUiView* pCurrentView) override
        {
            // PickPointExchangeData下面的参数中：
            // m_snappedPoint：返回值，返回捕捉得到的点。m_snappedPoint定义为类成员变量，可以在OnChildActionFinished获得返回值
            // m_isUserCancelled：返回值，返回IPickPointAction在捕捉过程中是否被用户取消。m_isUserCancelled定义为类成员变量，可以在OnChildActionFinished获得返回值
            // m_pickedPoints：同时是输入和返回值。在使用临时尺寸输入时会用到，例如传入的m_pickedPoints用于确定临时线型尺寸的起点。在捕捉成功返回后，新捕捉的点也会加入m_pickedPoints
            // m_opSnap：返回值，返回捕捉得到的ISnapCandidates::GetCurrentSnap()
            PickPointExchangeData exchangeData(&m_snappedPoint, &m_isUserCancelled, &m_pickedPoints, &m_opSnap);

            // 下面设置捕捉是二维捕捉，限于当前视图的工作平面
            exchangeData.m_pickPointType = PickPointExchangeData::PickPointType::TwoDimensionPoint;
            exchangeData.m_planeType = PickPointExchangeData::SnapPlaneType::WorkPlane;

            // 捕捉设置，例如ISnapSettings都在IPickPointActionConfig中设置。
            // 设计为回调函数的原因是在拾取过程中随时可以修改。例如用户在拾取过程中可以修改只捕捉某种特征点。
            OwnerPtr<IPickPointActionConfig> opPickConfigure = NEW_AS_OWNER_PTR(SamplePickPointConfigure);

            // 创建IPickPointAction。
            // 第二个参数是捕捉中鼠标移动的回调函数，设置nullptr是不做处理；一般会做创建构件的预览等操作。
            // ActionFinishedMode::LButtonDown：鼠标左键按下时结束Action
            // false：ActionBase的通用设置，设置回车键不会取消操作
            // opPickConfigure：各种捕捉设置
            gcmp::OwnerPtr<IPickPointAction> opPickPointAction = IPickPointAction::Create(exchangeData,
                nullptr, ActionFinishedMode::LButtonDown, false, TransferOwnership(opPickConfigure));

            // 设置IPickPointAction在拾取中的ISnapContext
            // 一般IPickPointAction会自动设置默认的ISnapContext，在捕捉需要定制时需要设置
            // UiSnapUtils::InitSnapContextFromUiView可以根据当前视图初始化ISnapContext的IViewerContext和拾取平面等内容
            IUiView* pUiView = UiDocumentViewUtils::GetCurrentUiView();
            ISnapContext* pSnapContext = opPickPointAction->GetSnapContext();
            UiSnapUtils::InitSnapContextFromUiView(pSnapContext, pUiView);

            // 启动IPickPointAction
            StartChildAction(TransferOwnership(opPickPointAction));
        }

        virtual void OnChildActionFinished(IUiView* pCurrentView, const ActionOutput& childActionReturnParam) override
        {
            if (m_isUserCancelled)
            {
                MarkFinishStatus(ActionFinishStatus::Cancelled);
            }
            else
            {
                // 这里可以根据捕捉结果做一些处理，例如创建图元等操作
                SnapType snapType = m_opSnap->GetSnapType();
                // 下面获得的捕捉点和m_snappedPoint是一致的，m_opSnap可以获得更多捕捉信息
                Vector3d snappedPoint = m_opSnap->GetSnappedPoint();
                MarkFinishStatus(ActionFinishStatus::Successful);
            }
        }

        // 缓存IPickPointAction捕捉到的点
        Vector3d m_snappedPoint;
        // 缓存IPickPointAction捕捉是否被用户取消
        bool m_isUserCancelled;
        // 缓存IPickPointAction捕捉到的点集合
        std::vector<Vector3d> m_pickedPoints;
        // 缓存缓存IPickPointAction的捕捉结果
        OwnerPtr<ISnap> m_opSnap;
    };

    /// InteractivePicker::GetPoint()捕捉过程中，鼠标移动的回调函数
    class SamplePickCallBack : public IPickCallback
    {

    public:
        virtual void OnMouseMove(IUiView* pCurrentView, const Vector3d &pickedPt, const Vector3d& mousePt) override
        {
            // 在用户移动鼠标寻找捕捉对应的过程中，可以做一些例如绘制临时对象的操作
        }
    };

    // InteractivePicker的捕捉演示命令
    class CmdSnapWithInteractivePicker : public gcmp::CommandBase
    {
    public:
        CmdSnapWithInteractivePicker();
        virtual ~CmdSnapWithInteractivePicker();

        virtual bool IsEnabled() const override { return GcmpCommandState::IsInDocumentEnvironment(); }
        virtual bool IsVisible() const override { return GcmpCommandState::IsInDocumentEnvironment(); }

        virtual gcmp::OwnerPtr<gcmp::IAction> ExecuteCommand(const gcmp::CommandParameters& cmdParams) override
        {
            // 创建并设置IPickPointAction在拾取中的ISnapContext
            // UiSnapUtils::InitSnapContextFromUiView可以根据当前视图初始化ISnapContext的IViewerContext和拾取平面等内容
            OwnerPtr<ISnapContext> opSnapContext = ISnapContext::Create();
            IUiView* pCurrentView = UiDocumentViewUtils::GetCurrentUiView();
            UiSnapUtils::InitSnapContextFromUiView(opSnapContext.get(), pCurrentView);

            // 获得全局的ISnapSettings，这里的程序希望在全局ISnapSettings的基础上，打开端点捕捉
            ISnapSettings* pGlobalSnapSettings = ISnapSettings::GetGlobalSnapSettings();
            // 因为对ISnapSettings会做修改，但是不希望改变全局的ISnapSettings，所以Clone一份使用
            OwnerPtr<ISnapSettings> opSnapSettings = pGlobalSnapSettings->Clone();

            // 首先关掉所有捕捉类型，然后打开需要的捕捉类型
            opSnapSettings->DisableAllSnaps();
            opSnapSettings->SetCanSnapEndPoint(true);
            opSnapSettings->SetCanSnapMiddlePoint(true);
            opSnapContext->SetSnapSettings(opSnapSettings.get());

            // 捕捉过程中鼠标移动的回调函数
            OwnerPtr<SamplePickCallBack> opSamplePickCallBack = NEW_AS_OWNER_PTR(SamplePickCallBack);

            // 捕捉到的点，用于返回值
            Vector3d pickedPoint;

            // 鼠标和状态栏的提示信息
            std::wstring promptMsg = L"InteractivePicker::GetPoint";

            // 启动拾取操作
            InteractivePicker::PickerResult result = InteractivePicker::GetPoint(opSnapContext.get(), nullptr, promptMsg, promptMsg,
                TransferOwnership(opSamplePickCallBack), pickedPoint, false, InteractivePicker::SnapPlaneType::WorkPlane, InteractivePicker::PickPointType::TwoDimensionPoint);

            if (result.FinishStatus == InteractivePicker::PickerFinishStatus::Successful)
            {
                // 可以用 pickedPoint 继续需要的操作，例如在点位置创建构件
            }

            return nullptr;
        }
    };

    /// 对捕捉候选集做排序，将相交捕捉放到最前面
    class SampleSnapProcessor : public ISnapCandidatesPreprocessor
    {

    public:
        virtual void Process(std::vector<OwnerPtr<ISnap>>& values) override
        {
            // 相交捕捉的集合
            std::vector<gcmp::OwnerPtr<gcmp::ISnap>> intersectSnaps;
            // 相交之外其他捕捉的集合
            std::vector<gcmp::OwnerPtr<gcmp::ISnap>> otherSnaps;

            // 将相交捕捉和其他捕捉分别放入容器
            for (auto& itor : values)
            {
                if (itor->GetSnapType() == SnapType::Intersect)
                {
                    intersectSnaps.push_back(TransferOwnership(itor));
                }
                else
                {
                    otherSnaps.push_back(TransferOwnership(itor));
                }
            }

            // 清空捕捉候选集，将相交捕捉和其他捕捉先后放入捕捉候选集
            values.clear();
            OwnerPtrContainerUtil::AddItems(values, intersectSnaps);
            OwnerPtrContainerUtil::AddItems(values, otherSnaps);
        }

    };
}
